#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# PyWikipediaBot based bot to be used with lyricwiki.org.
# Fetches album information (including cover art) from discogs and transfers 
# it to lyricwiki.org.
#
# Copyright (c) 2009, umat
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without 
# modification, are permitted provided that the following conditions are met:
#
# - Redistributions of source code must retain the above copyright notice, this 
# list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright notice, 
# this list of conditions and the following disclaimer in the documentation 
# and/or other materials provided with the distribution.
# - The names of the contributors may not be used to endorse or promote products 
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.



## TODO: Refactor this code and merge it with lybo to provide a generic abstraction for lyricwiki

import sys, os, re, getopt
from Discogs import Discogs
from upload import UploadRobot
import wikipedia

ignoreArtistCasing = False
ignoreAlbumCasing = False
dryRun = False

## Just a bunch of terminal colors to improve console output. ##
def color(t, c):
    return chr(0x1b)+"["+str(c)+"m"+t+chr(0x1b)+"[0m"
def black(t):
    return color(t, 30)
def red(t):
    return color(t, 31)
def green(t):
    return color(t, 32)
def yellow(t):
    return color(t, 33)
def blue(t):
    return color(t, 34)
def mangenta(t):
    return color(t, 35)
def cyan(t):
    return color(t, 36)
def white(t):
    return color(t, 37)
def bold(t):
    return color(t, 1)

def format_title(s):
    """
    Return the proper title casing of the given string.
    """
    global ignoreAlbumCasing
    if ignoreAlbumCasing:
	return s
    else:
	return s.title()

def format_artist(s):
    """
    Return the proper artist casing of the given string.
    """
    global ignoreArtistCasing
    if ignoreArtistCasing:
	return s
    else:
	return s.title()

def get_title(disc):
    """
    Return the title of the album in proper casing.
    """
    return format_title(disc.getRelTitle())

def get_artist(disc):
    """
    Return the artist of the album in proper casing.
    """
    return format_artist(disc.getRelArtist())

def get_title_year(disc):
    """
    Return the album title in the form:	
    Titel (YEAR)
    """
    return "%s (%s)" % (get_title(disc), disc.getRelYear())

def get_page_title(disc):
    """
    Return the album title in a form commonly used for page names:
    Artist:Titel (YEAR)
    """
    return "%s:%s" % (get_artist(disc), get_title_year(disc))

def get_cover_filename(disc):
    """
    Return the common filename for the cover art:
    Artist - Album.jpg
    """
    if get_cover(disc):
	filename = "%s - %s.jpg" % (get_artist(disc), get_title(disc))
	filename = filename.replace(":", "-") # Replace offending characters
	filename = filename.replace("/", "-") # Replace offending characters
	return filename
    return ""


def get_cover(disc):
    """
    Return the url of the first cover art image associated with the album.
    """
    if disc.getImageList() is None: return None
    return disc.getImageList()[0][2]

def upload_cover_art(disc):
    """
    Upload the first cover art associated with the album to the server
    """
    global dryRun
    url = get_cover(disc)
    keepFilename = True
    useFilename = get_cover_filename(disc)
    verifyDescription = False
    description = get_artwork_template(disc)
    print "Uploading %s as %s ..." % (bold(url), bold(useFilename))
    if not dryRun:
	bot = UploadRobot(url, description=description, useFilename=useFilename, keepFilename=keepFilename, verifyDescription=verifyDescription)
	bot.run()

def get_album_header(disc):
    """
    Return the template header for albums.
    """
    fLetter = get_title(disc).upper()[0]
    fLetter = re.sub(r"[0-9]", "0-9", fLetter)
    return "{{Album\n\
|Artist   = %s\n\
|Album    = %s\n\
|fLetter  = %s\n\
|Released = %s\n\
|Genre    = %s\n\
|Length   = %s\n\
|Cover    = %s\n\
|star     = Green\n\
}}\n" % (  get_artist(disc), 
           get_title(disc), 
           fLetter, 
           disc.getRelYear(), 
           disc.getGenre(), 
           get_length(disc), 
           get_cover_filename(disc) )

def get_album_footer(disc):
    """
    Return the template footer for albums.
    """
    return "{{AlbumFooter\n\
|artist   = %s\n\
|album    = %s\n\
|discogs  = %s\n\
}}\n" % ( get_artist(disc), get_title(disc), disc.getRelId() )

def get_artwork_template(disc):
    """
    Return the description template for cover arts.
    """
    return "{{Albumcover/Upload|\n\
|artist = %s\n\
|album  = %s\n\
|year   = %s\n\
}}" % ( get_artist(disc), get_title(disc), disc.getRelYear() )

def format_track(track):
    """
    Return the given track name formatted so it is conform with lyricwiki standards.
    - Make every first letter of a word upper-case and the rest lower-case
    - Replace #, <, [ and ] accordingly
    - Remove everything within brackets (really needed?)

    See: http://lyricwiki.org/LyricWiki:Page_Names
    """
    track = track.title().replace("'S","'s") # fix capitalization
    track = track.replace('#', 'Number')
    track = track.replace('<', 'Less Than')
    track = track.replace('[', '(')
    track = track.replace(']', ')')

    # Remove everything within brackets (might not be optimal)
    track = re.sub(r"\(.*?\)", "", track)

    # Strip useless spaces
    track = track.strip()
    return track

def get_tracklist(disc):
    """
    Return the wikified list of tracks on the album.
    """
    result = []
    for track in disc.getTrackList():
	if track[1]:
	    if len(format_track(track[3])) > 0:
		result.append("# '''[[%s:%s|%s]]'''" % (format_artist(track[2]), format_track(track[3]), track[3]))
	else:
	    result.append("===%s===" % track[3])
	    
    return u'\n'.join(result)

def get_list_header(disc):
    """
    Return the section header for the album to be used on the artist page.
    """
    result = []
    result.append("==[[%s|%s]]==" % (get_page_title(disc), get_title_year(disc)))
    if get_cover(disc):
	result.append("{{Album Art|%s|%s}}" % (get_cover_filename(disc), get_title_year(disc)))
    return u'\n'.join(result)

def get_overview_entry(disc):
    """
    Return the complete section of the album to be used on the artist page.
    """
    return "%s\n%s\n{{clear}}\n\n" % ( get_list_header(disc), get_tracklist(disc) )

def get_full_entry(disc):
    """
    Return the text of the full album page.
    """
    return "%s\n%s\n%s" % ( get_album_header(disc), get_tracklist(disc), get_album_footer(disc) )

def get_length(disc):
    """
    Compute length of the complete album, based on the sum of all track lengths.
    """
    length = 0
    for track in disc.getTrackList():
	if not track[1]: continue
	if not track[4]: return ""
	m,s = track[4].split(":")
	length += 60*int(m)+int(s)
    if length >= 3600:
	return "%02d:%02d:%02d" % ( length / 3600, (length % 3600) / 60 , length % 60 )
    elif length > 60:
	return "%02d:%02d" % ( length / 60 , length % 60 )
    elif length > 0: 
	return length
    else:
	return ""

def get_new_artist_page(disc):
    """
    Return the text of a new artist page with the album as it's only entry.
    """
    fLetter = get_artist(disc).upper()[0]
    fLetter = re.sub(r"[0-9]", "0-9", fLetter)
    result = u"{{ArtistHeader\n| star      = Green\n| country   = \n| state     = \n| hometown  = \n}}\n"
    result += get_overview_entry(disc)
    result += u"{{Artist\n| wikipedia    = \n| artist       = %s\n| officialSite = \n| fLetter      = %s\n| genre        = %s\n}}" % (get_artist(disc), fLetter,disc.getGenre())
    return result

def update_overview(disc, title):
    """
    Update the artist overview page by adding the current album as a section.
    Tries to places the album in the right chronological spot by comparing 
    the years of the releases.
    """
    global dryRun
    site = wikipedia.getSite()
    page = wikipedia.Page(site, title)
    try:
        text = page.get(get_redirect = True)
	r = re.search("(?si)#REDIRECT\s*\[\[(.*)\]\]", text)
	if r:
	    title = r.group(1)
	    print "Redirect to %s ..." % bold(title)
	    update_overview(disc, title)
	    return

	print "Updating artist page %s ..." % bold(title)
	try:
	    result = []
	    ryear = int(disc.getRelYear())
	    done = False
	    for line in text.split("==[["): ## Iterate through album years for chronological placement
		r = re.search("(?mi).*\((.*?)\).*", line)
		if r:
		    cyear = int(r.group(1))
		    if cyear >= ryear and not done:
			newline = get_overview_entry(disc)[4:]
			result.append(newline.encode("utf-8"))
		        done = True
		line = line.encode("utf-8")
		result.append(line)

	    text = unicode("==[[".join(result), "utf-8")

	    if not done:
    		newline = get_overview_entry(disc)		
		if re.search("(?si).*?==\s*Other\s*==", text): # put before ==Other==
		    text = re.sub("(?si)(.*?)(==\s*Other\s*==.*)", '\\1'+newline+'\\2', text)
		if re.search("(?si).*?==\s*Other Songs\s*==", text): # put before ==Other Songs==
		    text = re.sub("(?si)(.*?)(==\s*Other Songs\s*==.*)", '\\1'+newline+'\\2', text)
		elif re.search("(?si).*?\{\{Artist\s*\n.*", text): # put before {{Artist
		    text = re.sub("(?si)(.*?)(\{\{Artist\s*\n.*)", '\\1'+newline+'\\2', text)
		else:
		    print red("No suitable place found, to put the album on the artists page.")
		    return
	    comment = "Adding album %s with data from http://www.discogs.com/release/%s to artist page" % (get_title(disc), disc.getRelId())
	except:
	    print red("Failed to generate updated artist page!")
    	    import traceback
    	    print >>sys.stderr, "Uncaught exception:", str(ex)
    	    print >>sys.stderr, traceback.format_exc()
	    return
    except:
	print green("Artist page is empty, creating new one ...")
	print "Creating artist page %s ..." % bold(title)
	comment = "Creating artist page and adding album %s with data from http://www.discogs.com/release/%s" % (get_title(disc),disc.getRelId())
	text = get_new_artist_page(disc)

    if dryRun:
	print mangenta(comment)
        print bold(black(text))
    else:
        page.put(text, comment=comment)

def create_album_page(disc):
    """
    Create the new album page.
    """
    global dryRun
    title = get_page_title(disc)
    print "Creating album page %s ..." % bold(title)
    comment = "Creating album page with data from http://www.discogs.com/release/%s" % disc.getRelId()
    text = get_full_entry(disc)
    if dryRun:
	print mangenta(comment)
	print bold(black(text))
    else:
	site = wikipedia.getSite()
	page = wikipedia.Page(site, title)
	try:
	    page.get(get_redirect = True)
	    print yellow("Page already exists, overwriting! ....")
	    comment = "Overwriting current version of the album page with data from http://www.discogs.com/release/%s" % disc.getRelId()
	except:
	    print green("Page does not exist, creating new one ....")
        page.put(text, comment=comment)


def usage():
    """
    Display usage information.
    """
    progname = os.path.basename(sys.argv[0])
    print "Usage: %s [OPTIONS] [DISCOGS ID]" % bold(progname)
    print "Transfer data from Discogs to LyricWiki.org."
    print
    print "  -c, --create-album            Create album page"
    print "  -u, --upload-cover-art        Upload cover art"
    print "  -o, --update-overview         Update overview (artist page)"
    print "  -a, --all                     Perform all actions"
    print "  -i, --ignore-artist-casing    Ignore casing rules for artist "
    print "                                (use as provided by Discogs)"
    print "  -t, --ignore-album-casing     Ignore casing rules for album"
    print "                                (use as provided by Discogs)"
    print "  -d, --dry-run                 Dry run"
    print
    print bold(red("Please always manually check the results created by this bot."))
    print "For questions: http://lyricwiki.org/User_talk:Umat"
    print

    
def main(args):
    global ignoreArtistCasing, dryRun
    args = wikipedia.handleArgs()
#    wikipedia.put_throttle.setDelay(20.0, absolute = True)
    updateOverview = False
    uploadCoverArt = False
    createAlbumPage = False
    try:
        opts, args = getopt.getopt(args, "cuovaitdh", 
		["create-album", "upload-cover-art", "update-overview", "ignore-artist-casing", 
		 "ignore-album-casing", "dry-run", "help", "all"])
    except getopt.GetoptError, err:
        print "Error: %s" % red(str(err))
        usage()
        return 2

    for o, a in opts:
        if o == "-v":
            verbose = True # not used yet...
        elif o in ('-h', '--help'):
            usage()
            return 0
        elif o in ('-c', '--create-album'):
	    createAlbumPage = True
        elif o in ('-o', '--update-overview'):
            updateOverview = True
        elif o in ('-u', '--upload-cover-art'):
	    uploadCoverArt = True
        elif o in ('-a', '--all'):
            updateOverview = True
	    uploadCoverArt = True
	    createAlbumPage = True
        elif o in ('-i', '--ignore-artist-casing'):
	    ignoreArtistCasing = True
        elif o in ('-t', '--ignore-album-casing'):
	    ignoreTitleCasing = True
        elif o in ('-d', '--dry-run'):
	    dryRun = True
        else:
            assert False, "unhandled option"
            usage()
	    return

    if len(args) != 1: 
	usage()
	return 2

    discId = args[0]

    if not discId:
	usage()
	return

    disc = Discogs(discId)

    print "Performing requested actions for %s by %s ..." % (bold(get_title(disc)), bold(get_artist(disc)))

    if uploadCoverArt:
	upload_cover_art(disc)
    if createAlbumPage:
	create_album_page(disc)
    if updateOverview:
	update_overview(disc, get_artist(disc))

if __name__ == "__main__":
    try:
        main(sys.argv[1:])
    finally:
	wikipedia.stopme()
